package com.copm.ifm.message;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.copm.ifm.base.basic.ServiceResponse;
import com.copm.ifm.base.basic.enums.ResponseCodeEnum;
import com.copm.ifm.base.basic.exception.ServiceException;
import com.copm.ifm.base.basic.util.DateUtils;
import com.copm.ifm.common.redis.RedisUtil;
import com.copm.ifm.message.constance.RabbitMqCommon;
import com.copm.ifm.message.constance.RedisCommon;
import com.copm.ifm.message.entity.NewsMsgTemplate;
import com.copm.ifm.message.entity.SendTemplateDto;
import com.copm.ifm.message.entity.StaffReceive;
import com.copm.ifm.message.msg.IMessage;
import com.copm.ifm.message.send.EMSSend;
import com.copm.ifm.message.send.EmailSend;
import com.copm.ifm.servers.sys.entity.NewTiming;
import com.copm.ifm.servers.sys.entity.SysStaff;
import com.copm.ifm.servers.sys.service.NewTimingService;
import com.copm.ifm.servers.sys.service.staff.SysStaffService;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用来统一模板发送，发送的内容必须要从模板中获取，否则则看作为非正常途径获取（用来规范发送内容）
 * 1.前端根据类型选择对应的消息模板（所有的模板内容都储存在redis中）
 * 包含模板id、定时发送时间、发送内容、接收人集合、
 * 1> 校验前端参数正确性
 * 2> 查询redis中的模板信息
 * 3>
 *
 * @author tp
 */
@Component
public class TemplateSend {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    NewTimingService newTimingService;

    @Autowired
    SysStaffService sysStaffService;

    /**
     * (消息中心暂未将发送的消息体进行入库保存)
     * redis的模板消息储存方式（key+模板id，key+模板id，消息字符串）
     * 1.校验模板信息；
     * 2.根据模板id选择对应的模板消息体（所有的模板一开始就按照固定的前缀存入redis库中）
     * 3.如果有该模板消息：
     * 1> 查询出所有的模板消息；
     * 2> 获取对应的模板消息体信息（前缀key+模板id，键值key+模板id）；
     * 3> 校验格式（消息体符合规则）；
     * 4> 如果包含定时时间，就延迟发送消息体：
     * 1> 保存消息内容并入库；
     * 2> 延迟发送至mq等待被消费处理；
     * 5> 包装好消息进行批量发送
     */
    public ServiceResponse<?> send(SendTemplateDto<StaffReceive> sendTemplateDto) {
        NewsMsgTemplate template;
        //校验前端传入模板的正确性
        checkReceiveType(sendTemplateDto);
        //根据id查询模板信息
        boolean hasTemplate = RedisUtil.getInstance().hashKey(RedisCommon.NEWS_TEMP_PRE, RedisCommon.TEMP_PRE + sendTemplateDto.getTemplateId());
        if (hasTemplate) {
            //获取redis中的模板内容
            String templateString = (String) RedisUtil.getInstance().
                    hGetField(RedisCommon.NEWS_TEMP_PRE, RedisCommon.TEMP_PRE + sendTemplateDto.getTemplateId());

            //补充：String jsonStr = JSONObject.toJSONString(entity);  使用这种方式进行对象保存在redis中（互转）
            template = JSONObject.parseObject(templateString, NewsMsgTemplate.class);
            //校验消息内容的格式（根据长度）
            if (StringUtils.isEmpty(template)) {
                throw new ServiceException("库中没有对应的消息模板体，请联系管理员添加");
            }
            //校验模板内容格式
            checkMessageFormat(template, sendTemplateDto.getContent());
            String channelCode = template.getChannelCode();
            //如果包含延迟发送的时间，那么就发送至队列中等待消费
            if (!StringUtils.isEmpty(sendTemplateDto.getSendTime())) {
                //获取消息变量
                String[] formatMes = sendTemplateDto.getContent().split(",");
                String format = ServiceException.parseInfo(template.getContent(), (Object) formatMes);
                //保存数据
                NewTiming newTiming = handlerTimerSave(sendTemplateDto, format);
                //消息延迟发送(消息模板id做唯一标识)
                handlerTimerSend(sendTemplateDto, newTiming.getTempId());
                return new ServiceResponse<>(ResponseCodeEnum.SUCCESS);
            }
            List<StaffReceive> users;
            //如果是发送给设定的接收人的逻辑
            if (0 == sendTemplateDto.getSendType()) {
                users = receiveUsers(sendTemplateDto.getSystemUser());
            } else {
                users = sendTemplateDto.getReceiveUser();
            }
            //批量发送
            matchingSend(sendTemplateDto, users, channelCode);

        }
        return new ServiceResponse<>(ResponseCodeEnum.SUCCESS);
    }

    /**
     * 消息内容入库，任务消息入数据库，接收人入redis库
     *
     * @param dto     消息体
     * @param content 内容
     */
    public NewTiming handlerTimerSave(SendTemplateDto<StaffReceive> dto, String content) {
        List<StaffReceive> users;
        if (0 == dto.getSendType()) {
            //receiveUser没有值则使用模板接收人群
            users = receiveUsers(dto.getSystemUser());
            if (ObjectUtils.isEmpty(users)) {
                throw new ServiceException("无效的接收目标");
            }
        }
        users = dto.getReceiveUser();
        //将该接收人数据存入redis中（因为这块数据是临时的，没必要入库搞的那么麻烦，消费了之后就删的）
        users.forEach(s -> {
            Map<String, String> map = new HashMap<>(10);
            map.put("username", s.getUsername());
            map.put("phone", s.getPhone());
            map.put("name", s.getName());
            map.put("tenantId", String.valueOf(s.getTenantId()));
            RedisUtil.getInstance().add(String.valueOf(s.getId()), map);
        });

        //定时任务入库（这部分只存入接收人的idList做为关联条件）
        NewTiming timing = new NewTiming();
        timing.setContent(content);
        timing.setSendTime(DateUtils.strToDate(dto.getSendTime()));
        timing.setSecond((int) DateUtils.dateSubtraction(DateUtils.getSystemTime(), dto.getSendTime()));
        timing.setIsSend(0);
        timing.setTempId(dto.getTemplateId());
        if (dto.getSendType() == 0) {
            timing.setReceiveUser(dto.getSystemUser().stream().map(String::valueOf).collect(Collectors.joining(",")));
        } else {
            List<Long> userList = dto.getReceiveUser().stream().map(StaffReceive::getId).collect(Collectors.toList());
            timing.setReceiveUser(userList.stream().map(String::valueOf).collect(Collectors.joining(",")));
        }
        newTimingService.save(timing);
        return timing;
    }

    /**
     * 批量发送
     *
     * @param sendTemplateDto 消息体内容
     * @param users           接收人
     * @param channelCode     发送渠道（比如：ems、email。。。。）
     */
    public void matchingSend(SendTemplateDto<StaffReceive> sendTemplateDto, List<StaffReceive> users, String channelCode) {
        MessageFactory factory = new MessageFactory();
        IMessage emsSend = factory.create(null);
        if (channelCode.equals(SendClientEnum.MSG.code)) {
            emsSend = factory.create(EMSSend.class);
        }
        if (channelCode.equals(SendClientEnum.APP.code)) {
            emsSend = factory.create(EMSSend.class);
        }
        if (channelCode.equals(SendClientEnum.EMAIL.code)) {
            emsSend = factory.create(EmailSend.class);
        }
        if (channelCode.equals(SendClientEnum.WEB.code)) {
            emsSend = factory.create(EmailSend.class);
        }
        emsSend.send(sendTemplateDto, users);
    }


    /**
     * 1.校验接收人信息；
     * 2.校验接收定时时间；
     */
    public void checkReceiveType(SendTemplateDto<StaffReceive> dto) {
        if (1 == dto.getSendType()) {
            if (ObjectUtils.isEmpty(dto.getReceiveUser())) {
                throw new ServiceException("消息接收者不能为空");
            }
        }
        if (!StringUtils.isEmpty(dto.getSendTime())) {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date date = format.parse(dto.getSendTime());
                if (date.before(new Date())) {
                    throw new ServiceException("消息发送失败，且发送时间必须大于当前时间");
                }
            } catch (ParseException e) {
                throw new ServiceException("消息发送失败，无效的发送时间。正确的格式为：yyyy-MM-dd HH:mm:ss");
            }
        }
    }

    /**
     * 检测发送的模板内容格式
     *
     * @param template    redis中的模板内容
     * @param sendContent 前端传入的模板内容
     */
    private void checkMessageFormat(NewsMsgTemplate template, String sendContent) {
        //判断模板里面的内容是否存在替换内容，且判断传入的内容是否和模板里面的内容长度是否一致
        MessageFormat temp = new MessageFormat(template.getContent());
        int count = temp.getFormats().length;
        //不等于零表示存在需要替换的内容，length的大小表示需要替换的内容有多少个
        if (count != 0) {
            //传入的内容长度和模板的需要替换的内容长度做对比，不一致表示传入的参有误
            if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isBlank(sendContent)) {
                throw new ServiceException("消息发送失败，缺少必要的模板内容参数。" +
                        "模板内容：【" + template.getContent() + "】" + "传入的参数数据:【" + sendContent + "】");
            }
            String[] split = sendContent.split(",");
            int length = split.length;
            if (count != length) {
                throw new ServiceException("消息发送失败，缺少必要的模板内容参数。" +
                        "模板内容：【" + template.getContent() + "】" + "传入的参数数据:【" + sendContent + "】");
            }
        }
    }

    /**
     * 该发送方式只发送任务消息id，接受者接受到直接消费即可
     */
    public void handlerTimerSend(SendTemplateDto<StaffReceive> sendTemplateDto, Long correlationId) {
        int delay = 0;
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date date = format.parse(sendTemplateDto.getSendTime());
            delay = (int) DateUtils.dateTogether(new Date(), date);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        CorrelationData correlationData = new CorrelationData();
        //发送信息添加唯一标识。
        correlationData.setId(RabbitMqCommon.DELAYED_CORRELATION_ID_PRE + correlationId);
        int finalDelay = delay;
        rabbitTemplate.convertAndSend(RabbitMqCommon.DELAYED_EXCHANGE_NAME,
                RabbitMqCommon.DELAYED_ROUTING_KEY, RabbitMqCommon.DELAYED_CORRELATION_ID_PRE + correlationId, (message) -> {
                    //设置消息的过期时间毫秒
                    message.getMessageProperties().setDelay(finalDelay * 1000);
                    message.getMessageProperties().setCorrelationId(RabbitMqCommon.DELAYED_CORRELATION_ID_PRE + correlationId);
                    return message;

                }, correlationData);
    }

    /**
     * 消息模板发送时所接收人群的处理
     *
     * @param systemUser 接收人群
     * @return 返回模板人群信息
     */
    private List<StaffReceive> receiveUsers(List<Integer> systemUser) {
        List<StaffReceive> receiveList = new ArrayList<>();
        List<SysStaff> systemList = sysStaffService.list(new LambdaQueryWrapper<SysStaff>()
                .in(SysStaff::getId, systemUser));
        systemList.forEach(s -> {
            StaffReceive staffReceive = new StaffReceive();
            staffReceive.setTenantId(s.getTenantId());
            staffReceive.setUsername(s.getUsername());
            staffReceive.setPhone(s.getPhone());
            staffReceive.setId(s.getId());
            staffReceive.setTenantId(s.getTenantId());
            receiveList.add(staffReceive);
        });
        return receiveList;
    }

}
